package mybatis.session.defaults;

import mybatis.exception.MyBatisException;
import mybatis.executor.Executor;
import mybatis.mapping.MappedStatement;
import mybatis.session.Configuration;
import mybatis.session.SqlSession;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.sql.SQLException;
import java.util.List;

public class DefaultSqlSession implements SqlSession {

    private final Configuration configuration;
    private final Executor executor;
    private final boolean autoCommit;
    private boolean dirty = false;

    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
        this.configuration = configuration;
        this.executor = executor;
        this.autoCommit = autoCommit;
    }

    @Override
    public <T> T selectOne(String statement, Object... parameters) {
        List<T> records = selectList(statement, parameters);
        if (records.size() > 1) {
            throw new MyBatisException("查询结果不唯一。");
        }
        if (records.size() == 0) {
            return null;
        }
        return records.get(0);
    }

    @Override
    public <E> List<E> selectList(String statement, Object... parameters) {
        MappedStatement mappedStatement = configuration.getMappedStatement(statement);
        try {
            return executor.query(mappedStatement, parameters);
        } catch (SQLException ex) {
            throw new MyBatisException(ex);
        }
    }

    @Override
    public int insert(String statement, Object parameter) {
        return update(statement, parameter);
    }

    @Override
    public int update(String statement, Object parameter) {
        MappedStatement mappedStatement = configuration.getMappedStatement(statement);
        try {
            return executor.update(mappedStatement, parameter);
        } catch (SQLException ex) {
            throw new MyBatisException(ex);
        }
    }

    @Override
    public int delete(String statement, Object parameter) {
        return update(statement, parameter);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getMapper(Class<T> mapperClass) {
        Object proxyInstance = Proxy.newProxyInstance(mapperClass.getClassLoader(),
                new Class[]{mapperClass}, (proxy, method, args) -> {

                    // 获取调用的查询方法ID
                    String methodName = method.getName();
                    String interfaceName = method.getDeclaringClass().getName();
                    String statement = String.format("%s.%s", interfaceName, methodName);

                    // 判断方法调用的返回值类型
                    Type returnType = method.getGenericReturnType();
                    Object result = null;
                    // 如果当前方法调用的返回值有泛型定义并且返回的结果是集合则执行多结果函数调用
                    if ((returnType instanceof ParameterizedType) && isCollection(returnType)) {
                        result = selectList(statement, args);
                        // 否则判断是否是增删改方法
                    } else if (returnType.getTypeName().contains("int")) {
                        switch (method.getName()) {
                            case "insert":
                                result = insert(statement, args[0]);
                                break;
                            case "update":
                                result = update(statement, args[0]);
                                break;
                            case "delete":
                                result = delete(statement, args[0]);
                                break;
                            default:
                        }
                    } else {    // 否则就是单结果查询
                        result = selectOne(statement, args);
                    }
                    return result;
                });
        return (T) proxyInstance;
    }

    @Override
    public void commit() {
        this.commit(false);
    }

    @Override
    public void commit(boolean force) {
        try {
            this.executor.commit(this.isCommitOrRollbackRequired(force));
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void rollback() {
        this.rollback(false);
    }

    @Override
    public void rollback(boolean force) {
        try {
            executor.commit(this.isCommitOrRollbackRequired(force));
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
    }

    private boolean isCollection(Type type) {
        String className = ((ParameterizedType) type).getRawType().getTypeName();
        switch (className) {
            case "java.util.List":
            case "java.util.Map":
            case "java.util.Set":
                return true;
            default:
                return false;
        }
    }

    private boolean isCommitOrRollbackRequired(boolean force) {
        return !this.autoCommit && this.dirty || force;
    }

    @Override
    public void close() {
        this.executor.close(this.isCommitOrRollbackRequired(false));
        this.dirty = false;
    }

    @Override
    public Configuration getConfiguration() {
        return this.configuration;
    }

}
